// http服务器
$.srvHttp = class srvHttp extends $.srvHandle {
	/**
	 * http请求处理
	 * @param $srv object 原生创建http.createServer实例
	 * @param $req object 请求实例 见:http.createServer
	 * @param $res object 响应实例 见:http.createServer
	 * @return $.srvHttp
	 */
	constructor($srv, $req, $res){
		let path='/'+$req.url.replace(/[?#].*$/,'');
		const way=$req.method;  path=$.path(path);
		const head=`# [\x1B[90m{date}\x1B[0m] {use}`
			+` \x1B[90m{acs}\x1B[0m ${way}:`+path;
		super({mode:'proc',type:'http',head});
		return Object.defineProperties(this, {
			$srv: {enumerable:true,value:$srv},
			$req: {enumerable:true,value:$req},
			$res: {enumerable:true,value:$res},
			path: {enumerable:true,value:path},
			way: {enumerable:true,value:way},
			__srvHttp: {value:{}},
		});
	}
	/**
	 *
	 * 创建http服务器  http/https/http2
	 * @param opt json 创建服务器选项  原生+额外
	 * 		- 含pfx或key+cert: 使用SSL @前缀:文件
	 * 		http2: boolean 是否http2  默认:false
	 * 			非加密http2,可以创建,但无浏览支持
	 * @param back function 回调  back(hand)
	 * 		hand=new handle()实例, 每次请求新实例
	 * @return Object 原生httpServer实例  重写:
	 * 		listen(opt,auto=false):Promise
	 * 			opt: int|string|object  详见:原生
	 * 				IPC时:自动递归创建目录(非win)
	 * 			auto: boolean 是否自增找可用端口
	 * 				仅监听端口有效,监听IPC无效
	 */
	static create(opt, back){
		const $FS = require('node:fs');
		const SSL = (opt.key && opt.cert) || opt.pfx;
		const HTTP = opt['http2'] ? '2' : (SSL?'s':'');
		const H=require('node:http'+HTTP).createServer;
		SSL && ['pfx','key','cert'].forEach(k=>{ //证书
			let v=opt[k];  if(!v || v[0]!=='@') return;
			opt[k]=$FS.readFileSync(v.slice(1),'utf8');
		});
		const eKey = '$.srvHttp.create.back';
		const eRes = '$.srvHttp.create.res500';
		const SRV = H(opt, async(req,res)=>{
			const $H = new $.srvHttp(SRV, req, res);
			try{ await back($H); }catch(e){
				e = $.err(eKey, e, true);
				if(res.writableEnded) return $H.log(e);
				try{ await $H.res500(e); }
				catch(e){ $H.log($.err(eRes,e,true)); }
			}
			await $H.__end__(); // 仍有异常->全局捕获
		});
		const raw=SRV.listen, listen=opt=>{
			return new Promise((Y,N)=>{
				const E=e=>N(e);  SRV.on('error',E);
				const v = opt.port || opt.path;
				let B=()=>{ SRV.off('error',E); Y(v); };
				raw.call(SRV, opt, B);
			});
		};
		const AUTO = async function(o){
			let v=0, E=()=>v=0;  o.port--;
			while(!v){ v=++o.port; await listen(o).catch(E); }
			return v;
		};
		SRV.listen = function(opt, auto=false){
			if((opt*1)===(opt*1)) opt = {port:opt};
			if(typeof opt==='string') opt={path:opt};
			if(!opt.path || opt.port) // 监听端口
				return auto?AUTO(opt):listen(opt);
			if(process.platform==='win32'){
				opt.path='\\\\?\\'+opt.path;
				return listen(opt);
			}
			const m=$FS.promises.mkdir, p=opt.path;
			const w=m($.path(p,1),{recursive:true});
			return w.then(()=>listen(opt));
		};
		return SRV;
	}

	/**
	 * [配置]实例初始化
	 * @var $route json 解析的路由  $.router()
	 * 		注意: 由调用实例化后赋值  TODO ...
	 * @var $ctr object 控制器实例
	 // * @var $most array 默认及最高优先语言类型
	 // * 		- [默认语言(必需), 最高优先语言]
	 // * 		最优先: 通常用户选择, 小写,'_'分割
	 // *			$most:['zh_cn',''],
	 * @var htm_dir json|null 响应html方法映射
	 * 		- 若使用resHtml()响应正常数据,则必需
	 * 		htm_dir[path].call(this, data, $ctr)
	 * @var htm_err function|null 响应html异常
	 * 		- null: 使用内置简易的异常响应输出
	 * 		this.html_err(err.get(), $ctr|null)
	 */
	$route=null; $ctr=null; htm_dir=null; htm_err=null;
	/**
	 * [内置]实例化定义
	 * @var $srv object 原生创建http.createServer实例
	 * @var $req object 请求实例 见:http.createServer
	 * @var $res object 响应实例 见:http.createServer
	 * @var path string 请求路径 $.path(), 保留大小写
	 * @var way string 请求方法 GET, POST, ...
	 */
	$srv=null; $req=null; $res=null; path=''; way=null;

	/**
	 * 初始化解析body数据(POST)
	 * @return Promise<Object> {post, file}
	 */
	async initBody(){
		const T=this.__srvHttp.initBody={post:{},file:{}};
		let rtn;  this.initBody=()=>rtn;
		if(this.way!=='POST') return rtn=Promise.resolve(T);
		let typ=this.header('content_type')||'', tmp='';
		typ = typ.split(';')[0];
		if(typ.slice(0,19)==='multipart/form-data')
			return rtn = this.#__bodyForm__().then(()=>T);
		this.$req.on('data', val=>tmp+=val.toString());
		const END = ()=>{
			T.raw = tmp; // 收到body的原始数据
			if(typ==='application/json')
				try{ T.post=JSON.parse(tmp); }catch(e){}
			else if(typ==='application/x-www-form-urlencoded')
				T.post = require('node:querystring').parse(tmp);
		};
		return rtn = new Promise((done,fail)=>{
			this.$req.on('end', ()=>{
				try{ END(); done(T); }catch(e){ fail(e); }
			});
		});
	}

	/**
	 * 取得请求路径后缀
	 * @param dot boolean 返回是否包含'.'
	 * @return string 无后缀始终:'',无'.'
	 */
	ext(dot=false){
		let T = this.__srvHttp.ext;
		if(T===undefined){
			T=this.path.split('/').pop();
			let n = T.indexOf('.')>0;
			T = n?T.split('.').pop():'';
			this.__srvHttp.ext = T;
		}
		return T ? (dot?'.':'')+T : '';
	}
	/**
	 * 取得客户端IP
	 * @return string
	 */
	ipV4(){
		let T=this.__srvHttp.ipV4;  if(T) return T;
		T = this.$req.connection.remoteAddress;
		return this.__srvHttp.ipV4=T.split(':').pop();
	}
	/**
	 * 取得参数  post>que>cookie>header
	 * @param k string 要取得的KEY
	 * @return mixed|null
	 */
	any(k){
		return this.post(k) ?? this.que(k) ??
			this.cookie(k) ?? this.header(k);
	}
	/**
	 * 取得.请求:头信息
	 * @param k string 取得KEY  null:全部
	 * 		统一: 小写, 字符'-'转为'_'
	 * @return Object|string|null
	 */
	header(k=null){
		let T = this.__srvHttp.header;
		if(!T){
			T = this.__srvHttp.header = {};
			const H = this.$req.headers;
			for(const [k,v] of Object.entries(H))
				T[k.toLowerCase().replace(/-/g,'_')]=v;
		}
		return k===null ? T : (T[k]??null);
	}
	/**
	 * 取得请求cookie
	 * @param k string 取得KEY  null:全部
	 * @return Object|string|null
	 */
	cookie(k=null){
		let T = this.__srvHttp.cookie;
		if(!T){
			T = this.__srvHttp.cookie = {};
			const M = this.header('cookie');
			const B=s=>{ s=s.split('='); T[s[0]]=s[1]; };
			if(M) for(let v of M.split(';')) B(v.trim());
		}
		return k===null ? T : (T[k]??null);
	}
	/**
	 * 取得URL的QueryString参数
	 * @param k string|null 取得KEY  null:全部
	 * @return Object|mixed|null
	 */
	que(k=null){
		let T = this.__srvHttp.que;
		if(!T){
			let qs = this.$req.url.split('?')[1];
			if((qs=qs?qs.split('#')[0]:null)){
				let $QS=require('node:querystring');
				T = $QS.parse(qs);
			}
			this.__srvHttp.que = T || {};
		}
		return k===null ? T : (T[k]??null);
	}
	/**
	 * 取得POST数据  必须initBody()之后
	 * @param k string|null 取得KEY null:全部
	 * @return Object|mixed|null
	 */
	post(k=null){
		if(this.way!=='POST') return null;
		let T = this.__srvHttp.initBody?.post;
		if(!T) $.err('$.srvHttp.post','initBody');
		return k===null ? T : (T[k]??null);
	}
	/**
	 * 取得POST文件  必须initBody()之后
	 * @param k string|null 取得KEY null:全部
	 * @return Object|mixed|null
	 */
	file(k=null){
		let T = this.__srvHttp.initBody?.file;
		if(!T) $.err('$.srvHttp.post','initBody');
		return k===null ? T : (T[k]??null);
	}
	/**
	 * 收到body的原始数据  必须initBody()之后
	 * @return string|null
	 */
	body(){ return this.__srvHttp.initBody?.raw??null; }

	/**
	 * 设置响应状态码  res*前调用
	 * @param v int 状态码
	 * @return this
	 */
	setCode(v){ this.$res.statusCode=v; return this; }
	/**
	 * 设置响应MIME类型  res*前调用
	 * 		setHeader('Content-Type', ...)
	 * @param val string MIME类型值 或 后缀名(不含'.')
	 * @param ext string|null 方式及默认后缀  默认:null
	 * 		null:参数val为MIME类型值, 否则:默认后缀名
	 * @param enc string [后缀]文本类型时追加编码 '':不追加
	 * @return this
	 */
	setMime(val, ext=null, enc='utf-8'){
		if(ext!==null) val = $.mime(val,ext,enc);
		this.$res.setHeader('Content-Type', val);
		return this;
	}
	/**
	 * 设置响应头  res*前调用
	 * @param mix json|string 设置数据
	 * 		string:key,  json:多个
	 * @param val string|null 值 mix=json:忽略
	 * @return this
	 */
	setHeader(mix, val=null){
		if(typeof mix==='object')
			for(const [k,v] of Object.entries(mix))
				this.$res.setHeader(k, v);
		else this.$res.setHeader(mix, val);
		return this;
	}
	/**
	 * 设置响应cookie  res*前调用
	 * @param key string cookie名  内部,无检测
	 * @param val string|null cookie值  null:移除 内部,无检测
	 * @param opt json|null 选项
	 * 		pre: string cookie名的前缀
	 * 		exp: int 有效期 int>0(秒) 0:session过期  默认:604800=7天
	 * 		path: string|null 路径  null:当前路径  默认:'/'
	 * 		host: string|null 域名(当前域名或其父级域名)  null:当前域名  注意:不带端口
	 * 		secure boolean 是否安全连接发送  注意:true时http协议下不发送
	 * 		httponly boolean 是否仅允许服务器操作(提高安全性)  注意:true时浏览器js无法操作
	 * 		samesite string|null 限制第三方Cookie  None|Lax|Strict 详见:
	 * 			https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Set-Cookie/SameSite
	 * 			https://www.ruanyifeng.com/blog/2019/09/cookie-samesite.html
	 * @return this
	 */
	setCookie(key, val, opt){
		let v=key+'='+val, exp=opt?.exp??604800;  if(exp) exp+=this.time;
		if(val===null) v += '; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0';
		else if(exp) v+=`; expires=${new Date(exp).toUTCString()}; Max-Age=`+exp;
		v+=(opt?.path?'; path='+opt.path:'')+(opt?.host?'; domain='+opt.host:'');
		if(opt?.secure) v+='; secure';  if(opt?.httponly) v+='; httponly';
		if(opt?.samesite) v+='; samesite='+opt.samesite;
		return this.$res.setHeader('Set-Cookie',v);
	}

	/**
	 * 代理转发
	 * @param url string 转发目标URL http/https
	 * @param opt json|null 选项
	 * 		origin: string 头origin 默认:移除
	 * 		referer: string 头referer 默认:移除
	 * 		host: string 头host 默认:移除
	 * @param fail function|null 异常回调
	 * 		null ? log($.err()) : fail(err)
	 * @return this
	 */
	proxy(url, opt=null, fail=null){
		const H = this.$res.headers;
		for(const k of ['origin','referer','host'])
			if(opt?.[k]) H[k]=opt[k]; else delete H[k];
		const OPT = {method:this.$req.method,headers:H};
		const m='node:'+url.split(':')[0].toLowerCase();
		let $p=require(m).request(url,OPT,pxy=>{
			const {statusCode, headers} = pxy;
			this.$res.writeHead(statusCode, headers);
			pxy.pipe(this.$res);
		});
		$p.on('error', err=>{
			err = $.err('$.srvHttp.proxy',err,true);
			fail ? fail(err) : this.log(err);
		});
		this.$req.pipe($p);  return this;
	}
	/**
	 * 响应:json
	 * @param res mixed|null 响应数据
	 * 		非null+非object:转为{data}
	 * @param err object|null 异常信息
	 */
	resJson(res=null, err=null){
		if(!res || typeof res!=='object')
			res = res===null ? {} : {data:res};
		if(!err) res._ekey = res._ekey ?? '';
		else{
			err=$.err('$.srvHttp.resJson',err,true);
			const dts = $.mix({}, err.get());
			if(dts.ext && typeof dts.ext==='object'){
				$.mix(res,dts.ext);  delete dts.ext;
			}
			if(dts.sys){ // 系统异常
				const key=res._ekey='$.srv.500';
				res._emsg = this.msg(key);
			}
			else{ // 外部异常
				const key = res._ekey = dts.key;
				res._emsg = this.msg(key, dts.val);
				res._args=dts.val; res._exts=dts.ext;
			}
		}
		res._time = $.date(null,'t');
		if(!$.DEBUG) res = JSON.stringify(res);
		else{
			const dts = err?$.mix({},err.get()):null;
			if($.SRV) res.__srv=$.SRV; let k=dts?.key;
			if(k==='$.srv.404') res.__uri=this.$route;
			else if(dts) res.__err = dts;
			res.__log = this.log(null, 'full');
			if($._log_save){
				res.__log=$._log_save(res.__log,this.H);
				if(dts) dts.fix = $._log_save(dts.fix);
			}
			res = JSON.stringify(res, null, 4);
			this.log('response: '+res);
		}
		if(err) this.log(err); //记录异常日志
		return this.resRaw(res, 200, 'json');
	}
	/**
	 * 响应:html
	 * @param data json|null 响应数据
	 * @param err object|null 异常信息
	 */
	resHtml(data, err=null){
		if(!err){
			const fn = this.htm_dir[this.path];
			data = fn.call(this,data,this.$ctr);
			return this.resRaw(data,200,'html');
		}
		err = $.err('$.srvHttp.resHtml',err,true);
		if(this.htm_err) data=this.htm_err(err,this.$ctr);
		else{
			let {sys,key,val}=err.get(), ekey, emsg;
			if(sys) emsg = this.msg(ekey='$.srv.500');
			else emsg = this.msg(ekey=key, val);
			data='<p style="color:#888;">'+ekey+'</p>';
			data = '<h2>'+emsg+'</h2>'+data;
			if($.DEBUG){
				const dts = err?$.mix({},err.get()):null;
				dts.log = this.log(null, 'full');
				if($._log_save){
					if(dts.log) dts.log = $._log_save(dts.log,this);
					if(dts.fix) dts.fix = $._log_save(dts.fix);
				}
				dts.fix = dts.fix.trim().split('\n').map(v=>v.trim());
				let v='\n'+JSON.stringify(dts,null,4)+'\n';
				data+='\n\n<pre id="__debug__">'+v+'</pre>';
			}
		}
		this.log(err); // 记录异常日志
		return this.resRaw(data, 200, 'html');
	}
	/**
	 * 响应:文件
	 * @param file string 文件的完整路径
	 * @param opt json|null 选项  TODO 缓存控制
	 * 		ext: string|boolean 后缀,无. 设置mime
	 * 			true:自动(默认), false:不设
	 * @param fail function|null 异常回调
	 * 		null ? res404('file') : fail(err)
	 * @return this
	 */
	resFile(file, opt=null, fail=null){
		let ext = opt?.ext ?? true;
		if(ext===true) ext=file.split('.').pop();
		if(ext) this.setMime(ext, '*');
		const $FS = require('node:fs');
		let $stream = $FS.createReadStream(file);
		if(!fail) fail = ()=>this.res404('file');
		$stream.on('error', fail);
		$stream.pipe(this.$res);  return this;
	}
	/**
	 * 响应:404
	 * @param ekey string 异常KEY
	 * @param code int 响应状态码
	 * @param type string|null 类型 json|html
	 * 		null: 根据请求类型  GET?html:json
	 * @return Promise
	 */
	res404(ekey, code=200, type=null){
		type = type||(this.way==='GET'?'html':'json');
		const uri=this.$route;  if(uri) uri.err=ekey;
		const err = $.err('@$.srv.404', {ekey}, true);
		return type==='json' ? this.resJson(null,err)
			: this.resHtml(null,err);
	}
	/**
	 * 响应:500
	 * @param err mixed 异常信息
	 * @param code int 响应状态码  仅系统异常
	 * @param type string|null 类型 json|html
	 * 		null: 根据请求类型  GET?html:json
	 * @return Promise
	 */
	res500(err, code=200, type=null){
		err = $.err('$.srvHttp.res500', err, true);
		type = type||(this.way==='GET'?'html':'json');
		if(err.get('sys')) this.$res.statusCode=code;
		return type==='json' ? this.resJson(null,err)
			: this.resHtml(null,err);
	}
	/**
	 * 响应:原始数据
	 * @param str string 响应数据
	 * @param code int 响应状态码
	 * @param ext string|null 后缀 设置mime
	 * @return Promise
	 */
	resRaw(str, code=200, ext=null){
		this.$res.statusCode = code;
		if(ext) this.setMime(ext, 'html');
		return new Promise(Y=>this.$res.end(str,Y));
	}

	/**
	 * 接受语言  优先排序
	 * @return array
	 */
	// most(){
	// 	// if(this.$lang) return [this.$lang]; // 禁用多语言处理
	// 	const T=this.__srvHttp.most;  if(T) return T;  let s=[];
	// 	const a = this.header('accept_language').replace(/-/g,'_');
	// 	const A = a.replace(/\s+/g,'').toLowerCase().split(',');
	// 	const PUSH = (k,v)=>{ v*=1; s.push({k,v:v===v?v:1}); };
	// 	for(let v of A) PUSH(v.split(';q='));  const m=this.$most;
	// 	s.sort((a,b)=>a.v>b.v?-1:1); s=s.map(v=>v.k); m.push(m[0]);
	// 	if(s[0]) s.unshift(m[0]);  return this.__srvHttp.most=s;
	// },

	// TODO: [内部]解析body:formData
	async #__bodyForm__(){
		$.err('$.srvHttp.__bodyForm__.todo');
		// let dir=$.TEMP+'/srv-post-file/'+this.acs;
		// await require('node:fs').promises.mkdir(dir,{recursive:true})
	}
};
// 默认语言
$.lang.reg('$.srv', {
	zh_cn:{404:'路径未找到',500:'通信错误,请稍后再试.'},
});
